# Licensed to the Apache Software Foundation (ASF) under one or more contributor
# license agreements.  See the NOTICE file distributed with this work for
# additional information regarding copyright ownership.  The ASF licenses this
# file to you under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License.  You may obtain a copy of
# the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
# License for the specific language governing permissions and limitations under
# the License.

# * Find Arrow (arrow/api.h, libarrow.a, libarrow.so) This module defines
#   ARROW_FOUND, whether Arrow has been found ARROW_FULL_SO_VERSION, full shared
#   object version of found Arrow "100.0.0" ARROW_IMPORT_LIB, path to libarrow's
#   import library (Windows only) ARROW_INCLUDE_DIR, directory containing
#   headers ARROW_LIBS, deprecated. Use ARROW_LIB_DIR instead ARROW_LIB_DIR,
#   directory containing Arrow libraries ARROW_SHARED_IMP_LIB, deprecated. Use
#   ARROW_IMPORT_LIB instead ARROW_SHARED_LIB, path to libarrow's shared library
#   ARROW_SO_VERSION, shared object version of found Arrow such as "100"
#   ARROW_STATIC_LIB, path to libarrow.a ARROW_VERSION, version of found Arrow
#   ARROW_VERSION_MAJOR, major version of found Arrow ARROW_VERSION_MINOR, minor
#   version of found Arrow ARROW_VERSION_PATCH, patch version of found Arrow

if(DEFINED ARROW_FOUND)
  return()
endif()

find_package(PkgConfig)
include(FindPackageHandleStandardArgs)

if(WIN32 AND NOT MINGW)
  # This is used to handle builds using e.g. clang in an MSVC setting.
  set(MSVC_TOOLCHAIN TRUE)
else()
  set(MSVC_TOOLCHAIN FALSE)
endif()

set(ARROW_SEARCH_LIB_PATH_SUFFIXES)
if(CMAKE_LIBRARY_ARCHITECTURE)
  list(APPEND ARROW_SEARCH_LIB_PATH_SUFFIXES
       "lib/${CMAKE_LIBRARY_ARCHITECTURE}")
endif()
list(APPEND ARROW_SEARCH_LIB_PATH_SUFFIXES "lib64" "lib32" "lib" "bin")
set(ARROW_CONFIG_SUFFIXES "_RELEASE" "_RELWITHDEBINFO" "_MINSIZEREL" "_DEBUG"
                          "")
if(CMAKE_BUILD_TYPE)
  string(TOUPPER ${CMAKE_BUILD_TYPE} ARROW_CONFIG_SUFFIX_PREFERRED)
  set(ARROW_CONFIG_SUFFIX_PREFERRED "_${ARROW_CONFIG_SUFFIX_PREFERRED}")
  list(INSERT ARROW_CONFIG_SUFFIXES 0 "${ARROW_CONFIG_SUFFIX_PREFERRED}")
endif()

if(NOT DEFINED ARROW_MSVC_STATIC_LIB_SUFFIX)
  if(MSVC_TOOLCHAIN)
    set(ARROW_MSVC_STATIC_LIB_SUFFIX "_static")
  else()
    set(ARROW_MSVC_STATIC_LIB_SUFFIX "")
  endif()
endif()

# Internal function.
#
# Set shared library name for ${base_name} to ${output_variable}.
#
# Example: arrow_build_shared_library_name(ARROW_SHARED_LIBRARY_NAME arrow) # ->
# ARROW_SHARED_LIBRARY_NAME=libarrow.so on Linux # ->
# ARROW_SHARED_LIBRARY_NAME=libarrow.dylib on macOS # ->
# ARROW_SHARED_LIBRARY_NAME=arrow.dll with MSVC on Windows # ->
# ARROW_SHARED_LIBRARY_NAME=libarrow.dll with MinGW on Windows
function(arrow_build_shared_library_name output_variable base_name)
  set(${output_variable}
      "${CMAKE_SHARED_LIBRARY_PREFIX}${base_name}${CMAKE_SHARED_LIBRARY_SUFFIX}"
      PARENT_SCOPE)
endfunction()

# Internal function.
#
# Set import library name for ${base_name} to ${output_variable}. This is useful
# only for MSVC build. Import library is used only with MSVC build.
#
# Example: arrow_build_import_library_name(ARROW_IMPORT_LIBRARY_NAME arrow) # ->
# ARROW_IMPORT_LIBRARY_NAME=arrow on Linux (meaningless) # ->
# ARROW_IMPORT_LIBRARY_NAME=arrow on macOS (meaningless) # ->
# ARROW_IMPORT_LIBRARY_NAME=arrow.lib with MSVC on Windows # ->
# ARROW_IMPORT_LIBRARY_NAME=libarrow.dll.a with MinGW on Windows
function(arrow_build_import_library_name output_variable base_name)
  set(${output_variable}
      "${CMAKE_IMPORT_LIBRARY_PREFIX}${base_name}${CMAKE_IMPORT_LIBRARY_SUFFIX}"
      PARENT_SCOPE)
endfunction()

# Internal function.
#
# Set static library name for ${base_name} to ${output_variable}.
#
# Example: arrow_build_static_library_name(ARROW_STATIC_LIBRARY_NAME arrow) # ->
# ARROW_STATIC_LIBRARY_NAME=libarrow.a on Linux # ->
# ARROW_STATIC_LIBRARY_NAME=libarrow.a on macOS # ->
# ARROW_STATIC_LIBRARY_NAME=arrow.lib with MSVC on Windows # ->
# ARROW_STATIC_LIBRARY_NAME=libarrow.dll.a with MinGW on Windows
function(arrow_build_static_library_name output_variable base_name)
  set(${output_variable}
      "${CMAKE_STATIC_LIBRARY_PREFIX}${base_name}${ARROW_MSVC_STATIC_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}"
      PARENT_SCOPE)
endfunction()

# Internal function.
#
# Set macro value for ${macro_name} in ${header_content} to ${output_variable}.
#
# Example: arrow_extract_macro_value(version_major "ARROW_VERSION_MAJOR"
# "#define ARROW_VERSION_MAJOR 1.0.0") # -> version_major=1.0.0
function(arrow_extract_macro_value output_variable macro_name header_content)
  string(REGEX MATCH "#define +${macro_name} +[^\r\n]+" macro_definition
               "${header_content}")
  string(REGEX REPLACE "^#define +${macro_name} +(.+)$" "\\1" macro_value
                       "${macro_definition}")
  set(${output_variable}
      "${macro_value}"
      PARENT_SCOPE)
endfunction()

# Internal macro only for arrow_find_package.
#
# Find package in HOME.
macro(arrow_find_package_home)
  find_path(
    ${prefix}_include_dir "${header_path}"
    PATHS "${home}"
    PATH_SUFFIXES "include"
    NO_DEFAULT_PATH)
  set(include_dir "${${prefix}_include_dir}")
  set(${prefix}_INCLUDE_DIR
      "${include_dir}"
      PARENT_SCOPE)

  if(MSVC_TOOLCHAIN)
    set(CMAKE_SHARED_LIBRARY_SUFFIXES_ORIGINAL ${CMAKE_FIND_LIBRARY_SUFFIXES})
    # .dll isn't found by find_library with MSVC because .dll isn't included in
    # CMAKE_FIND_LIBRARY_SUFFIXES.
    list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES "${CMAKE_SHARED_LIBRARY_SUFFIX}")
  endif()
  find_library(
    ${prefix}_shared_lib
    NAMES "${shared_lib_name}"
    PATHS "${home}"
    PATH_SUFFIXES ${ARROW_SEARCH_LIB_PATH_SUFFIXES}
    NO_DEFAULT_PATH)
  if(MSVC_TOOLCHAIN)
    set(CMAKE_SHARED_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_ORIGINAL})
  endif()
  set(shared_lib "${${prefix}_shared_lib}")
  set(${prefix}_SHARED_LIB
      "${shared_lib}"
      PARENT_SCOPE)
  if(shared_lib)
    add_library(${target_shared} SHARED IMPORTED)
    set_target_properties(${target_shared} PROPERTIES IMPORTED_LOCATION
                                                      "${shared_lib}")
    if(include_dir)
      set_target_properties(
        ${target_shared} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
                                    "${include_dir}")
    endif()
    find_library(
      ${prefix}_import_lib
      NAMES "${import_lib_name}"
      PATHS "${home}"
      PATH_SUFFIXES ${ARROW_SEARCH_LIB_PATH_SUFFIXES}
      NO_DEFAULT_PATH)
    set(import_lib "${${prefix}_import_lib}")
    set(${prefix}_IMPORT_LIB
        "${import_lib}"
        PARENT_SCOPE)
    if(import_lib)
      set_target_properties(${target_shared} PROPERTIES IMPORTED_IMPLIB
                                                        "${import_lib}")
    endif()
  endif()

  find_library(
    ${prefix}_static_lib
    NAMES "${static_lib_name}"
    PATHS "${home}"
    PATH_SUFFIXES ${ARROW_SEARCH_LIB_PATH_SUFFIXES}
    NO_DEFAULT_PATH)
  set(static_lib "${${prefix}_static_lib}")
  set(${prefix}_STATIC_LIB
      "${static_lib}"
      PARENT_SCOPE)
  if(static_lib)
    add_library(${target_static} STATIC IMPORTED)
    set_target_properties(${target_static} PROPERTIES IMPORTED_LOCATION
                                                      "${static_lib}")
    if(include_dir)
      set_target_properties(
        ${target_static} PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
                                    "${include_dir}")
    endif()
  endif()
endmacro()

# Internal macro only for arrow_find_package.
#
# Find package by CMake package configuration.
macro(arrow_find_package_cmake_package_configuration)
  find_package(${cmake_package_name} CONFIG)
  if(${cmake_package_name}_FOUND)
    set(${prefix}_USE_CMAKE_PACKAGE_CONFIG
        TRUE
        PARENT_SCOPE)
    if(TARGET ${target_shared})
      foreach(suffix ${ARROW_CONFIG_SUFFIXES})
        get_target_property(shared_lib ${target_shared}
                            IMPORTED_LOCATION${suffix})
        if(shared_lib)
          # Remove shared library version: libarrow.so.100.0.0 -> libarrow.so
          # Because ARROW_HOME and pkg-config approaches don't add shared
          # library version.
          string(REGEX REPLACE "(${CMAKE_SHARED_LIBRARY_SUFFIX})[.0-9]+$" "\\1"
                               shared_lib "${shared_lib}")
          set(${prefix}_SHARED_LIB
              "${shared_lib}"
              PARENT_SCOPE)
          break()
        endif()
      endforeach()
    endif()
    if(TARGET ${target_static})
      foreach(suffix ${ARROW_CONFIG_SUFFIXES})
        get_target_property(static_lib ${target_static}
                            IMPORTED_LOCATION${suffix})
        if(static_lib)
          set(${prefix}_STATIC_LIB
              "${static_lib}"
              PARENT_SCOPE)
          break()
        endif()
      endforeach()
    endif()
  endif()
endmacro()

# Internal macro only for arrow_find_package.
#
# Find package by pkg-config.
macro(arrow_find_package_pkg_config)
  pkg_check_modules(${prefix}_PC ${pkg_config_name})
  if(${prefix}_PC_FOUND)
    set(${prefix}_USE_PKG_CONFIG
        TRUE
        PARENT_SCOPE)

    set(include_dir "${${prefix}_PC_INCLUDEDIR}")
    set(lib_dir "${${prefix}_PC_LIBDIR}")
    set(shared_lib_paths "${${prefix}_PC_LINK_LIBRARIES}")
    # Use the first shared library path as the IMPORTED_LOCATION for
    # ${target_shared}. This assumes that the first shared library path is the
    # shared library path for this module.
    list(GET shared_lib_paths 0 first_shared_lib_path)
    # Use the rest shared library paths as the INTERFACE_LINK_LIBRARIES for
    # ${target_shared}. This assumes that the rest shared library paths are
    # dependency library paths for this module.
    list(LENGTH shared_lib_paths n_shared_lib_paths)
    if(n_shared_lib_paths LESS_EQUAL 1)
      set(rest_shared_lib_paths)
    else()
      list(SUBLIST shared_lib_paths 1 -1 rest_shared_lib_paths)
    endif()

    set(${prefix}_VERSION
        "${${prefix}_PC_VERSION}"
        PARENT_SCOPE)
    set(${prefix}_INCLUDE_DIR
        "${include_dir}"
        PARENT_SCOPE)
    set(${prefix}_SHARED_LIB
        "${first_shared_lib_path}"
        PARENT_SCOPE)

    add_library(${target_shared} SHARED IMPORTED)
    set_target_properties(
      ${target_shared}
      PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${include_dir}"
                 INTERFACE_LINK_LIBRARIES "${rest_shared_lib_paths}"
                 IMPORTED_LOCATION "${first_shared_lib_path}")
    get_target_property(shared_lib ${target_shared} IMPORTED_LOCATION)

    find_library(
      ${prefix}_static_lib
      NAMES "${static_lib_name}"
      PATHS "${lib_dir}"
      NO_DEFAULT_PATH)
    set(static_lib "${${prefix}_static_lib}")
    set(${prefix}_STATIC_LIB
        "${static_lib}"
        PARENT_SCOPE)
    if(static_lib)
      add_library(${target_static} STATIC IMPORTED)
      set_target_properties(
        ${target_static}
        PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${include_dir}"
                   IMPORTED_LOCATION "${static_lib}")
    endif()
  endif()
endmacro()

function(
  arrow_find_package
  prefix
  home
  base_name
  header_path
  cmake_package_name
  pkg_config_name)
  arrow_build_shared_library_name(shared_lib_name ${base_name})
  arrow_build_import_library_name(import_lib_name ${base_name})
  arrow_build_static_library_name(static_lib_name ${base_name})

  set(target_shared ${base_name}_shared)
  set(target_static ${base_name}_static)

  if(home)
    arrow_find_package_home()
    set(${prefix}_FIND_APPROACH
        "HOME: ${home}"
        PARENT_SCOPE)
  else()
    arrow_find_package_cmake_package_configuration()
    if(${cmake_package_name}_FOUND)
      set(${prefix}_FIND_APPROACH
          "CMake package configuration: ${cmake_package_name}"
          PARENT_SCOPE)
    else()
      arrow_find_package_pkg_config()
      set(${prefix}_FIND_APPROACH
          "pkg-config: ${pkg_config_name}"
          PARENT_SCOPE)
    endif()
  endif()

  if(NOT include_dir)
    if(TARGET ${target_shared})
      get_target_property(include_dir ${target_shared}
                          INTERFACE_INCLUDE_DIRECTORIES)
    elseif(TARGET ${target_static})
      get_target_property(include_dir ${target_static}
                          INTERFACE_INCLUDE_DIRECTORIES)
    endif()
  endif()
  if(include_dir)
    set(${prefix}_INCLUDE_DIR
        "${include_dir}"
        PARENT_SCOPE)
  endif()

  if(shared_lib)
    get_filename_component(lib_dir "${shared_lib}" DIRECTORY)
  elseif(static_lib)
    get_filename_component(lib_dir "${static_lib}" DIRECTORY)
  else()
    set(lib_dir NOTFOUND)
  endif()
  set(${prefix}_LIB_DIR
      "${lib_dir}"
      PARENT_SCOPE)
  # For backward compatibility
  set(${prefix}_LIBS
      "${lib_dir}"
      PARENT_SCOPE)
endfunction()

if(NOT "$ENV{ARROW_HOME}" STREQUAL "")
  file(TO_CMAKE_PATH "$ENV{ARROW_HOME}" ARROW_HOME)
endif()
arrow_find_package(ARROW "${ARROW_HOME}" arrow arrow/api.h Arrow arrow)

if(ARROW_HOME)
  if(ARROW_INCLUDE_DIR)
    file(READ "${ARROW_INCLUDE_DIR}/arrow/util/config.h" ARROW_CONFIG_H_CONTENT)
    arrow_extract_macro_value(ARROW_VERSION_MAJOR "ARROW_VERSION_MAJOR"
                              "${ARROW_CONFIG_H_CONTENT}")
    arrow_extract_macro_value(ARROW_VERSION_MINOR "ARROW_VERSION_MINOR"
                              "${ARROW_CONFIG_H_CONTENT}")
    arrow_extract_macro_value(ARROW_VERSION_PATCH "ARROW_VERSION_PATCH"
                              "${ARROW_CONFIG_H_CONTENT}")
    if("${ARROW_VERSION_MAJOR}" STREQUAL ""
       OR "${ARROW_VERSION_MINOR}" STREQUAL ""
       OR "${ARROW_VERSION_PATCH}" STREQUAL "")
      set(ARROW_VERSION "0.0.0")
    else()
      set(ARROW_VERSION
          "${ARROW_VERSION_MAJOR}.${ARROW_VERSION_MINOR}.${ARROW_VERSION_PATCH}"
      )
    endif()

    arrow_extract_macro_value(ARROW_SO_VERSION_QUOTED "ARROW_SO_VERSION"
                              "${ARROW_CONFIG_H_CONTENT}")
    string(REGEX REPLACE "^\"(.+)\"$" "\\1" ARROW_SO_VERSION
                         "${ARROW_SO_VERSION_QUOTED}")
    arrow_extract_macro_value(
      ARROW_FULL_SO_VERSION_QUOTED "ARROW_FULL_SO_VERSION"
      "${ARROW_CONFIG_H_CONTENT}")
    string(REGEX REPLACE "^\"(.+)\"$" "\\1" ARROW_FULL_SO_VERSION
                         "${ARROW_FULL_SO_VERSION_QUOTED}")
  endif()
else()
  if(ARROW_USE_CMAKE_PACKAGE_CONFIG)
    find_package(Arrow CONFIG)
  elseif(ARROW_USE_PKG_CONFIG)
    pkg_get_variable(ARROW_SO_VERSION arrow so_version)
    pkg_get_variable(ARROW_FULL_SO_VERSION arrow full_so_version)
  endif()
endif()

set(ARROW_ABI_VERSION ${ARROW_SO_VERSION})

mark_as_advanced(
  ARROW_ABI_VERSION
  ARROW_CONFIG_SUFFIXES
  ARROW_FULL_SO_VERSION
  ARROW_IMPORT_LIB
  ARROW_INCLUDE_DIR
  ARROW_LIBS
  ARROW_LIB_DIR
  ARROW_SEARCH_LIB_PATH_SUFFIXES
  ARROW_SHARED_IMP_LIB
  ARROW_SHARED_LIB
  ARROW_SO_VERSION
  ARROW_STATIC_LIB
  ARROW_VERSION
  ARROW_VERSION_MAJOR
  ARROW_VERSION_MINOR
  ARROW_VERSION_PATCH)

find_package_handle_standard_args(
  Arrow
  REQUIRED_VARS
    # The first required variable is shown in the found message. So this list is
    # not sorted alphabetically.
    ARROW_INCLUDE_DIR ARROW_LIB_DIR ARROW_FULL_SO_VERSION ARROW_SO_VERSION
  VERSION_VAR ARROW_VERSION)
set(ARROW_FOUND ${Arrow_FOUND})

if(Arrow_FOUND AND NOT Arrow_FIND_QUIETLY)
  message(STATUS "Arrow version: ${ARROW_VERSION} (${ARROW_FIND_APPROACH})")
  message(STATUS "Arrow SO and ABI version: ${ARROW_SO_VERSION}")
  message(STATUS "Arrow full SO version: ${ARROW_FULL_SO_VERSION}")
  message(STATUS "Found the Arrow core shared library: ${ARROW_SHARED_LIB}")
  message(STATUS "Found the Arrow core import library: ${ARROW_IMPORT_LIB}")
  message(STATUS "Found the Arrow core static library: ${ARROW_STATIC_LIB}")
endif()
